package com.zxw.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zxw.common.api.ApiResult;
import com.zxw.common.entity.gateway.BlackVO;
import com.zxw.common.entity.gateway.LogQuery;
import com.zxw.common.entity.user.UserVO;
import com.zxw.common.enums.ResultCode;
import com.zxw.common.utils.MD5Util;
import com.zxw.gateway.config.GatewaySkipUrlProperties;
import com.zxw.gateway.feign.UserServerFeignApi;
import com.zxw.gateway.service.RedisService;
import com.zxw.gateway.utils.EncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 全局filter，认证鉴权过滤器
 */
@Component
@Slf4j
public class GatewayFilterConfig implements GlobalFilter, Ordered {


    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private RedisService redisService;

    @Autowired
    private GatewaySkipUrlProperties gatewaySkipUrlProperties;

    @Autowired
    private UserServerFeignApi userServerFeignApi;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("=====================================网关过滤器执行=========================================");
        ServerHttpRequest request = exchange.getRequest();
        String requestUrl = request.getPath().value();
        log.info("请求URL：{}", requestUrl);

        // 保存请求日志
         writeLog2DB(request);

        // 黑名单拦截
        ApiResult<List<BlackVO>> apiResult = this.userServerFeignApi.queryList();
        if (ResultCode.SUCCESS.getCode() == apiResult.getCode()) {
            List<BlackVO> blackVOList = apiResult.getData();
            log.info("黑名单集合：{}", JSON.toJSONString(blackVOList));
            if (CollUtil.isNotEmpty(blackVOList)) {
                List<String> ips = blackVOList.stream().map(BlackVO::getIp).collect(Collectors.toList());
                log.info("ips：{}", JSON.toJSONString(ips));
                if (ips.contains(getIP(request))) {
                    return blackMono(exchange);
                }
            }
        }

        // 白名单跳过token校验
        AntPathMatcher pathMatcher = new AntPathMatcher();
        List<String> skipUrls = gatewaySkipUrlProperties.getInclude();
        log.info("白名单列表 skipUrls = {}", JSON.toJSONString(skipUrls));
        for (String url : skipUrls) {
            if (pathMatcher.match(url, requestUrl)) {
                request = exchange.getRequest().mutate().header(HttpHeaders.AUTHORIZATION, "").build();
                exchange = exchange.mutate().request(request).build();
                return chain.filter(exchange);
            }
        }

        // 检查token是否存在
        String token = getToken(exchange);
        log.info("获取header中的token, token = {}", token);
        if (StringUtils.isBlank(token)) {
            return noTokenMono(exchange);
        }

        // 判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = tokenStore.readAccessToken(token);
            Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
            log.info("解析token, additionalInformation = {}", JSON.toJSONString(additionalInformation));

            // 取出用户身份信息
            UserVO userVO = new UserVO();
            userVO.setId((String) additionalInformation.get("userId"));
            userVO.setUserName((String) additionalInformation.get("userName"));
            userVO.setNickName((String) additionalInformation.get("nickName"));
            userVO.setPhone((String) additionalInformation.get("phone"));
            userVO.setEmail((String) additionalInformation.get("email"));
            userVO.setIsActive((Boolean) additionalInformation.get("isActive"));
            userVO.setEnabled((Boolean) additionalInformation.get("enabled"));

            // 获取用户权限
            List<String> authorities = (List<String>) additionalInformation.get("authorities");
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("userVO", userVO);
            jsonObject.put("authorities", authorities);

            // 给header里面添加值
            String base64 = EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonObject));
            ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("json-token", base64).build();
            ServerWebExchange build = exchange.mutate().request(tokenRequest).build();

            // 将用户信息存入redis
            redisService.set(MD5Util.md5Encode(token), JSON.toJSONString(userVO), 60 * 60 * 24);
            return chain.filter(build);
        } catch (InvalidTokenException e) {
            log.error("无效的token: {}", token);
            return invalidTokenMono(exchange);
        }
    }

    @Async
    void writeLog2DB(ServerHttpRequest request) {
        try {
            // 访问日志写入数据库
            String ip = getIP(request);
            String requestUrl = request.getPath().value();
            LogQuery logQuery = new LogQuery();
            logQuery.setIp(ip);
            logQuery.setUrl(requestUrl);
            this.userServerFeignApi.logSave(logQuery);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("访问日志写入数据库出错！");
        }
    }


    // 获取token
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }


    // 无效的token
    private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
        return buildReturnMono(ApiResult.failed(ResultCode.INVALID_TOKEN), exchange);
    }

    // token不存在
    private Mono<Void> noTokenMono(ServerWebExchange exchange) {
        return buildReturnMono(ApiResult.failed(ResultCode.NO_TOKEN), exchange);
    }

    // 黑名单
    private Mono<Void> blackMono(ServerWebExchange exchange) {
        return buildReturnMono(ApiResult.failed(ResultCode.BLACK_FORBIDDEN), exchange);
    }

    // 自定义返回结果
    private Mono<Void> buildReturnMono(ApiResult<?> apiResult, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = JSONObject.toJSONString(apiResult).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.OK);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }

    // 多次反向代理后会有多个ip值 的分割符
    private final static String IP_UTILS_FLAG = ",";
    // 未知IP
    private final static String UNKNOWN = "unknown";
    // 本地 IP
    private final static String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private final static String LOCALHOST_IP1 = "127.0.0.1";

    private static String getIP(ServerHttpRequest request) {
        // 根据 HttpHeaders 获取 请求 IP地址
        String ip = request.getHeaders().getFirst("X-Forwarded-For");
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("x-forwarded-for");
            if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip)) {
                // 多次反向代理后会有多个ip值，第一个ip才是真实ip
                if (ip.contains(IP_UTILS_FLAG)) {
                    ip = ip.split(IP_UTILS_FLAG)[0];
                }
            }
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeaders().getFirst("X-Real-IP");
        }
        //兼容k8s集群获取ip
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
            if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                //根据网卡取本机配置的IP
                InetAddress iNet = null;
                try {
                    iNet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    log.error("getClientIp error: ", e);
                }
                ip = iNet.getHostAddress();
            }
        }
        return ip;
    }
}